<?php
namespace Org\AliAppPay;
require_once("alipay.config.php");
/**
 * Created by PhpStorm.
 * User: moko1988
 * Date: 16/4/26
 * Time: 上午11:25
 */
class AliPayApi{

    /*获取支付参数*/
    public function getPrePayParam($paramData = array()){

        $paramData['service'] = 'mobile.securitypay.pay';//接口名称
        $paramData['partner'] = ALIPAY_CONFIG_PARTNER;//合作者身份ID
        $paramData['_input_charset'] = ALIPAY_CONFIG_INPUT_CHARSET;//参数编码字符集
        $paramData['payment_type'] = '1';//支付类型
        $paramData['seller_id'] = ALIPAY_CONFIG_PARTNER;//卖家支付宝账号

        /*
         * 得到参与签名的字符串
         * ①去除待签名参数数组中的sign,sign_type,以及值为空的
         * ②将去除后的数组中的元素转成待签名字符串
         * 参与签名的字符串可以不做字典排序并且参数值必须用引号包起来(key1="val1"&key2="val2"这种形式的)
         * 错误的签名字符串  $signStr = 'subject=远行家定金&body=远行家定金&out_trade_no=moko1988
         * 正确的签名字符串  $signStr = 'subject="远行家定金"&body="远行家定金"&out_trade_no="moko1988"
         * */
        $signStr = $this->ArrToParams($paramData);

        //得到签名字符串 得到的签名字符串必须要urlencode
        $sign = $this->rsaSign($signStr, ALIPAY_CONFIG_PRIVATE_KEY_PATH);

        //把签名的结果和签名的方式两个字段加到签名字符串后面

        $signStr .= '&sign="'.$sign.'"&sign_type="'.ALIPAY_CONFIG_SIGN_TYPE.'"';
        //返回客户端需要的支付参数字符串
        return $signStr;

    }

    //把参数数组转成字符串
    private function ArrToParams($paraMap){

        $buff = "";
        foreach ($paraMap as $k => $v){
            if($k != "sign" && $k != "sign_type" && $v != ""){
                $buff .= $k . "=\"" . $v . "\"&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }

    /*
     * RSA签名
     * @param $data 待签名数据
     * @param $private_key_path 商户私钥文件路径
     * return 签名结果
     **/
    private function rsaSign($data, $private_key_path) {
        $priKey = file_get_contents($private_key_path);
        $res = openssl_pkey_get_private($priKey);
        openssl_sign($data, $sign, $res);
        openssl_free_key($res);
        //base64编码
        $sign = base64_encode($sign);
        //urlencode编码
        $sign = urlencode($sign);
        return $sign;
    }




    /*验证支付通知*/
    public function verifyNotify($paramData){

        if(empty($paramData)) {
            \Think\Log::write('支付宝通知验证失败:返回参数为空','INFO');
            $returnRes = false;
        }else{
            //验证签名
            $isSign = $this->getSignVeryfy($paramData, $paramData["sign"]);

            if($isSign){
                //验证通知是否是支付宝发来的
                $responseTxt = $this->getResponse($paramData["notify_id"]);

                if($responseTxt == 'true'){
                    $returnRes = true;
                }else{
                    \Think\Log::write('支付宝通知验证失败:通知不合法','INFO');
                    $returnRes = false;
                }
            }else{
                \Think\Log::write('支付宝通知验证失败:签名不通过','INFO');
                $returnRes = false;
            }


        }

        return $returnRes;
    }


    /*
     * 获取返回时的签名验证结果
     * @param $para_temp 通知返回来的参数数组
     * @param $sign 返回的签名结果
     * @return 签名验证结果
     */
    private function getSignVeryfy($para_temp, $sign) {

        //\Think\Log::write('支付宝通知参数:'.json_encode($para_temp),'INFO');
        ksort($para_temp);
        //\Think\Log::write('支付宝通知排序后参数:'.json_encode($para_temp),'INFO');

        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->ToUrlParams($para_temp);
        //\Think\Log::write('支付宝通知拼接的参数:'.$prestr,'INFO');

        $isSgin = $this->rsaVerify($prestr, ALIPAY_CONFIG_ALI_PUBLIC_KEY_PATH, $sign);

        return $isSgin;
    }


    //格式化参数格式化成url参数
    private function ToUrlParams($paraMap){

        $buff = "";
        foreach ($paraMap as $k => $v){
            if($k != "sign" && $k != "sign_type" && $v != ""){
                $buff .= $k . "=" . $v . "&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }

    /*
     * RSA验签
     * @param $data 待签名数据
     * @param $ali_public_key_path 支付宝的公钥文件路径
     * @param $sign 要校对的的签名结果
     * return 验证结果
     */
    private function rsaVerify($data, $ali_public_key_path, $sign)  {

        $pubKey = file_get_contents($ali_public_key_path);
        $res = openssl_get_publickey($pubKey);
        $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        openssl_free_key($res);
        return $result;
    }



    /*
     * 获取远程服务器ATN结果,验证返回URL
     * @param $notify_id 通知校验ID
     * @return 服务器ATN结果
     * 验证结果集：
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    private function getResponse($notify_id) {
        $transport = strtolower(trim(ALIPAY_CONFIG_TRANSPORT));
        $partner = trim(ALIPAY_CONFIG_PARTNER);

        if($transport == 'https') {
            $veryfy_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
            $isHttps = true;
        }
        else {
            $veryfy_url = 'http://notify.alipay.com/trade/notify_query.do?';
            $isHttps = false;
        }
        $veryfy_url = $veryfy_url."partner=" . $partner . "&notify_id=" . $notify_id;
        $responseTxt = $this->getHttpResponseGET($veryfy_url, $isHttps, ALIPAY_CONFIG_CACERT);

        return $responseTxt;
    }


    /*
     * 远程获取数据，GET模式
     * 注意：
     * 1.使用Crul需要修改服务器中php.ini文件的设置，找到php_curl.dll去掉前面的";"就行了
     * 2.文件夹中cacert.pem是SSL证书请保证其路径有效，目前默认路径是：getcwd().'\\cacert.pem'
     * @param $url 指定URL完整路径地址
     * @param $cacert_url 指定当前工作目录绝对路径
     * return 远程输出的数据
     */
    private function getHttpResponseGET($url, $isHttps, $cacert_url) {

        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
        if($isHttps){
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
            curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
        }
        $responseText = curl_exec($curl);
        //var_dump( curl_error($curl) );//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
        curl_close($curl);

        //var_dump($responseText);
        return $responseText;
    }
}